package cn.bugstack.wrench.dynamic.config.center.service;

import cn.bugstack.wrench.dynamic.config.center.config.DynamicConfigCenterAutoProperties;
import cn.bugstack.wrench.dynamic.config.center.model.valobj.AttributeVO;
import cn.bugstack.wrench.dynamic.config.center.types.annotations.DCCValue;
import cn.bugstack.wrench.dynamic.config.center.types.common.Constants;
import jodd.util.StringUtil;
import org.apache.commons.lang.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @author chenjia
 */
public class DynamicConfigCenterService implements IDynamicConfigCenterService{

    private final Logger logger = LoggerFactory.getLogger(DynamicConfigCenterService.class);

    private final RedissonClient redissonClient;

    private final DynamicConfigCenterAutoProperties properties;

    //使用 dccBeanGroup 记录已处理的 Bean，便于后续属性变更时快速定位。
    private final Map<String,Object> dccBeanGroup = new ConcurrentHashMap<>();

    public DynamicConfigCenterService(DynamicConfigCenterAutoProperties properties, RedissonClient redisson) {
        this.redissonClient = redisson;
        this.properties = properties;
    }


    /**
     * 用于对 Spring 容器中的 Bean 进行代理，扫描并注入带有 @DCCValue 注解的属性，实现属性的动态赋值。
     * @param bean bean
     * @return object
     */
    @Override
    public Object proxyObject(Object bean) {
        //为兼容 Spring AOP 代理，需通过 AopProxyUtils.getTargetClass 获取真实类，避免注解扫描失效。
        Class<?> targetBeanClass = bean.getClass();
        Object targetBeanObject = bean;
        if(AopUtils.isAopProxy(bean)){
            targetBeanClass = AopUtils.getTargetClass(bean);
            targetBeanObject = AopProxyUtils.getSingletonTarget(bean);
        }

        //注解扫描 ：遍历 Bean 的所有字段，筛选出带有 @DCCValue 注解的属性。
        Field[] fields = targetBeanClass.getDeclaredFields();
        for (Field field : fields) {
            if(!field.isAnnotationPresent(DCCValue.class)){
                continue;
            }
            //配置解析 ：解析注解 value，支持 key:defaultValue 格式，提取配置 key 和默认值。
            DCCValue dccValue = field.getAnnotation(DCCValue.class);
            String value = dccValue.value();
            if(StringUtil.isBlank(value)){
                throw new RuntimeException(field.getName() + "@DCCValue is not config value config case 「isSwitch/isSwitch:1」");
            }

            String[] split = value.split(Constants.SYMBOL_COLON);
            String key = properties.getKey(split[0].trim());
            String defaultValue = split.length == 2 ? split[1] : null;
            //设置值
            String setValue = defaultValue;
            try {
                //如果为空就抛出异常
                if(StringUtils.isBlank(defaultValue)){
                    throw new RuntimeException("dcc config error " + key + " defaultValue is null - 请配置默认值");
                }

                //Redis交互 ：优先从 Redis 获取配置值，若不存在则写入默认值。
                RBucket<String> bucket = redissonClient.getBucket(key);
                boolean exists = bucket.isExists();
                if(!exists){
                    bucket.set(value);
                }else {
                    setValue = bucket.get();
                }
                //属性注入 ：通过反射将配置值注入到目标属性。
                field.setAccessible(true);
                field.set(targetBeanObject, setValue);
                field.setAccessible(false);
            }catch (Exception e){
                throw new RuntimeException(e);
            }
            //Bean注册 ：将处理过的 Bean 按 key 注册到 dccBeanGroup，便于后续动态变更。
            dccBeanGroup.put(key, targetBeanObject);
        }

        return bean;
    }


    /**
     * 用于在接收到配置变更事件时，动态调整目标 Bean 的属性值。
     * @param attributeVO 属性值调整值对象
     */
    @Override
    public void adjustAttributeValue(AttributeVO attributeVO) {
        //属性定位 ：根据 AttributeVO 提供的属性名和 key，定位目标 Bean。
        String key = properties.getKey(attributeVO.getAttribute());
        String value = attributeVO.getValue();
        //Redis更新 ：同步更新 Redis 中的配置值。
        RBucket<String> bucket = redissonClient.getBucket(key);
        boolean exists = bucket.isExists();
        if(!exists){
            return;
        }
        Object objBean = dccBeanGroup.get(key);
        if(null == objBean){
            return;
        }
        bucket.set(attributeVO.getValue());
        //反射赋值 ：通过反射将新值注入目标 Bean 的对应属性，实现运行时动态变更。
        Class<?> objBeanClass = objBean.getClass();
        //检查objBean是否是代理对象
        if(AopUtils.isAopProxy(objBean)){
            //获取代理对象的目标对象
            objBeanClass = AopUtils.getTargetClass(objBean);
        }

        try {
            // 1. getDeclaredField 方法用于获取指定类中声明的所有字段，包括私有字段、受保护字段和公共字段。
            // 2. getField 方法用于获取指定类中的公共字段，即只能获取到公共访问修饰符（public）的字段。
            Field field = objBeanClass.getDeclaredField(attributeVO.getAttribute());
            field.setAccessible(true);
            field.set(objBean, value);
            field.setAccessible(false);

            logger.info("DCC 节点监听，动态设置值 {} {}", key, value);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
